package de.luh.slimpp.generator

import com.google.common.base.CharMatcher
import com.google.common.base.Strings
import de.luh.slimpp.slimPP.ArrayDeclaration
import de.luh.slimpp.slimPP.BaseClassName
import de.luh.slimpp.slimPP.ClassDefinition
import de.luh.slimpp.slimPP.Constructor
import de.luh.slimpp.slimPP.ConstructorInitializer
import de.luh.slimpp.slimPP.Literal
import de.luh.slimpp.slimPP.Member
import de.luh.slimpp.slimPP.MemberDeclaration
import de.luh.slimpp.slimPP.MemberInitialization
import de.luh.slimpp.slimPP.Method
import de.luh.slimpp.slimPP.Model
import de.luh.slimpp.slimPP.Parameter
import de.luh.slimpp.slimPP.QualifiedName
import de.luh.slimpp.slimPP.QualifiedTypeName
import de.luh.slimpp.slimPP.TemplateArgument
import de.luh.slimpp.slimPP.TemplateParameter
import de.luh.slimpp.slimPP.TemplateParameters
import de.luh.slimpp.slimPP.TypeDef
import de.luh.slimpp.slimPP.TypeName
import java.util.ArrayList
import java.util.HashMap
import java.util.List
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator

class SlimPPGenerator implements IGenerator {

	int indentationCount = 0
	ModelPreprocessor modelPreprocessor

	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		indentationCount = 0
		modelPreprocessor = new ModelPreprocessor
		println(resource.contents.size)
		if (resource.contents.size == 0) {
			return
		}
		val model = resource.contents.get(0) as Model
		modelPreprocessor.process(model)
		for (cls : model.eAllContents.toIterable.filter(typeof(ClassDefinition))) {
			fsa.generateFile(cls.qualifiedName.namespaces.join('', '/', '/', [it]) + cls.qualifiedName.name + '.h',
				cls.compileHeader)
		}
		for (cls : model.eAllContents.toIterable.filter(typeof(ClassDefinition)).filter[!type.interface]) {
			fsa.generateFile(cls.qualifiedName.namespaces.join('', '/', '/', [it]) + cls.qualifiedName.name + '.cpp',
				cls.compileImplementation)
		}
	}

	def getMethods(ClassDefinition cls) {
		cls.defs.filter(typeof(Method))
	}

	def getMembers(ClassDefinition cls) {
		cls.defs.filter(typeof(Member))
	}

	def compileHeader(ClassDefinition cls) {
		val x = new StringBuilder

		// include guard
		x.append('#pragma once')
		x.newLine(2)

		// dependencies on other types and headers
		x.append(cls.compileIncludes)

		//		x.append(cls.compileForwardDeclarations) //TODO
		//		x.newLine
		// declare all namespaces
		for (ns : cls.qualifiedName.namespaces) {
			x.append('namespace ' + ns + ' {')
			x.newLine
		}
		x.newLine(!cls.qualifiedName.namespaces.empty)

		// compile the class comment
		if (cls.comment != null) {
			x.append(cls.comment.compileComment)
			x.newLine
		}

		// declare the template parameters
		if (cls.template != null) {
			x.append(cls.template.compile)
			x.newLine
		}

		// class name
		x.append('class ')
		x.append(cls.qualifiedName.name)

		// base classes
		if (cls.bases != null && !cls.bases.empty) {
			x.append(' : ')
			x.append(cls.bases.map[compile].join(', '))
		}

		// after the { the class body starts
		x.append(' {')

		// build the map from member variable names to their types (as Strings)
		val idToTypeMap = new HashMap<String, String>
		for (member : cls.members) {
			member.variables.map[name].forEach[idToTypeMap.put(it, member.type.compile)]
		}
		val idToType = [String id|idToTypeMap.get(id)]

		var EnhancedClass enhancedClass = null
		if (modelPreprocessor != null) {
			enhancedClass = modelPreprocessor.getEnhancedClass(cls)
		}

		compilePublicHeader(x, cls, enhancedClass, idToType)
		if (!cls.type.interface) {
			compileProtectedHeader(x, cls, enhancedClass, idToType)
			compilePrivateHeader(x, cls, enhancedClass, idToType)
		}

		x.removeTrailingBlankLines
		x.newLine
		x.append('};')
		x.newLine(2)

		// append template method definitions here if necessary
		if (enhancedClass != null) {
			for (method : enhancedClass.methods.filter[it.mustBeDefinedInHeader]) {
				x.append(method.original.compileDefinition(cls, enhancedClass.typedefs))
				x.newLine(2)
			}
		}

		// close all namespaces
		for (ns : cls.qualifiedName.namespaces.reverseView) {
			x.append('} // namespace ' + ns)
			x.newLine
		}

		// end the header file with a single new line
		x.removeTrailingBlankLines
		x.newLine

		return x.toString
	}

	def compilePublicHeader(StringBuilder x, ClassDefinition cls, EnhancedClass enhancedClass, (String)=>String idToType) {

		// public area
		x.newLine
		x.append('public:')
		indent(1)
		x.newLine

		// public typedefs
		x.append(cls.typeDefs.filter[visibility == null || visibility.public].compileAll[it.compile])

		// public members
		x.append(cls.members.filter[visibility != null && visibility.public].compileAll[it.compileDeclaration])

		// public constructors
		val className = cls.qualifiedName.name
		x.append(
			cls.ctors.filter[visibility == null || visibility.public].compileAll(
				[it.compileDeclaration(className, idToType)]))

		// destructor
		// the destructor is virtual if:
		// - this class is sub-classed somewhere in the model
		// - the class is an interface or abstract
		val isDtorVirtual = cls.type.interface || cls.type.abstract // || cls.isSubclassed(model)
		if (cls.dtor || isDtorVirtual) {
			x.append(compileDestructorDeclaration(className, isDtorVirtual, cls.type.interface || !cls.dtor))
		}

		// public methods
		if (enhancedClass != null) {
			println(cls.qualifiedName.name + ' has ' + enhancedClass.methods.size + ' methods')
			for (method : enhancedClass.methods.filter[original.visibility == null || original.visibility.public]) {
				x.append(method.compileDeclaration)
				x.newLine(2)
			}
		} else {
			println(cls.qualifiedName.name + ' has no enhanced class')
			x.append(
				cls.methods.filter[visibility == null || visibility.public].compileAll[
					it.compileDeclaration(cls.type.interface)])
		}

		x.removeTrailingBlankLines
		indent(-1)

		if (x.toString.endsWith('public:')) {
			x.delete(x.length - 'public:'.length, x.length)
			x.removeTrailingBlankLines
		} else {
			x.newLine
		}
	}

	def compileProtectedHeader(StringBuilder x, ClassDefinition cls, EnhancedClass enhancedClass,
		(String)=>String idToType) { // protected area
		x.newLine
		x.append('protected:')
		indent(1)
		x.newLine // protected typedefs
		x.append(cls.typeDefs.filter[visibility != null && visibility.protected].compileAll[it.compile])

		// protected constructors
		val className = cls.qualifiedName.name
		x.append(
			cls.ctors.filter[visibility != null && visibility.protected].compileAll(
				[it.compileDeclaration(className, idToType)]))

		// protected methods
		if (enhancedClass != null) {
			println(cls.qualifiedName.name + ' has ' + enhancedClass.methods.size + ' methods')
			for (method : enhancedClass.methods.filter[original.visibility != null && original.visibility.protected]) {
				x.append(method.compileDeclaration)
				x.newLine(2)
			}
		} else {
			println(cls.qualifiedName.name + ' has no enhanced class')
			x.append(
				cls.methods.filter[visibility != null && visibility.protected].compileAll[
					it.compileDeclaration(cls.type.interface)])
		}

		// protected members
		x.append(cls.members.filter[visibility != null && visibility.protected].compileAll[it.compileDeclaration])

		x.removeTrailingBlankLines
		indent(-1)

		if (x.toString.endsWith('protected:')) {
			x.delete(x.length - 'protected:'.length, x.length)
			x.removeTrailingBlankLines
		} else {
			x.newLine
		}
	}

	def compilePrivateHeader(StringBuilder x, ClassDefinition cls, EnhancedClass enhancedClass,
		(String)=>String idToType) {

		// private area
		x.newLine
		x.append('private:')
		indent(1)
		x.newLine

		// private typedefs
		x.append(cls.typeDefs.filter[visibility != null && visibility.private].compileAll[it.compile])

		// private constructors
		val className = cls.qualifiedName.name
		x.append(
			cls.ctors.filter[visibility != null && visibility.private].compileAll(
				[it.compileDeclaration(className, idToType)]))

		// private methods
		if (enhancedClass != null) {
			println(cls.qualifiedName.name + ' has ' + enhancedClass.methods.size + ' methods')
			for (method : enhancedClass.methods.filter[original.visibility != null && original.visibility.private]) {
				x.append(method.compileDeclaration)
				x.newLine(2)
			}
		} else {
			println(cls.qualifiedName.name + ' has no enhanced class')
			x.append(
				cls.methods.filter[visibility != null && visibility.private].compileAll[
					it.compileDeclaration(cls.type.interface)])
		}

		// protected members
		x.append(cls.members.filter[visibility == null || visibility.private].compileAll[it.compileDeclaration])

		x.removeTrailingBlankLines
		indent(-1)

		if (x.toString.endsWith('private:')) {
			x.delete(x.length - 'private:'.length, x.length)
			x.removeTrailingBlankLines
		}
	}

	def compileImplementation(ClassDefinition cls) {
		val x = new StringBuilder

		x.append('#include "' + cls.qualifiedName.name + '.h"')
		x.newLine(2)

		for (ns : cls.qualifiedName.namespaces) {
			x.append('namespace ' + ns + ' {')
			x.newLine
		}
		x.newLine(!cls.qualifiedName.namespaces.empty)

		// define all static members
		for (member : cls.members.filter[static]) {
			for (variable : member.variables) {
				x.append(member.type.compile)
				x.append(' ' + cls.qualifiedName.name + '::')
				x.append(variable.compile(true) + ';')
				x.newLine
			}
		}
		if (!cls.members.filter[static].empty) {
			x.newLine
		}

		// define the constructors
		// build the map from member variable names to their types (as Strings)
		val idToTypeMap = new HashMap<String, String>
		for (member : cls.members) {
			member.variables.map[name].forEach[idToTypeMap.put(it, member.type.compile)]
		}
		val idToType = [String id|idToTypeMap.get(id)]
		for (ctor : cls.ctors) {
			x.append(ctor.compileDefinition(cls.qualifiedName.name, idToType, cls.template))
		}

		// define the destructor
		if (cls.dtor) {
			x.append(cls.qualifiedName.name)
			x.append('::~')
			x.append(cls.qualifiedName.name)
			x.append(' {')
			x.newLine
			x.append('}')
			x.newLine(2)
		}

		// define the methods
		var enhancedClass = modelPreprocessor.getEnhancedClass(cls)
		if (enhancedClass != null) {
			for (method : enhancedClass.methods.filter[mustBeDefined && !mustBeDefinedInHeader]) {
				x.append(method.original.compileDefinition(cls, enhancedClass.typedefs))
				x.newLine(2)
			}
		} else {
			val typeDefNames = new ArrayList<String>
			cls.typeDefs.forEach[typeDefNames.add(name)]
			x.append(cls.methods.compileAll[it.compileDefinition(cls, typeDefNames)])
		}

		// close all namespaces
		for (ns : cls.qualifiedName.namespaces.reverseView) {
			x.append('} // namespace ' + ns)
			x.newLine
		}
		
		x.removeTrailingBlankLines
		x.newLine

		return x.toString
	}

	def compileDestructorDeclaration(String className, boolean virtual, boolean define) {
		val x = new StringBuilder

		if (define) {
			x.append('/** Empty virtual destructor for sub-classes to override */')
			x.newLine
		}
		if (virtual) {
			x.append('virtual ')
		}
		x.append('~')
		x.append(className)
		x.append('()')
		if (define) {
			x.append(' {')
			x.newLine
			x.append('}')
		} else
			x.append(';')
		x.newLine(2)

		return x.toString
	}

	def <T> compileAll(Iterable<T> items, (T)=>String compiler) {
		val x = new StringBuilder
		for (item : items) {
			x.append(compiler.apply(item))
			x.newLine(2)
		}
		return x.toString
	}

	def compileIncludes(ClassDefinition cls) {
		val enhancedClass = modelPreprocessor.getEnhancedClass(cls)
		if (enhancedClass == null) {
			throw new Exception('anhanced calss not found')
		}
		val x = new StringBuilder
		for (include : enhancedClass.includes) {
			x.append('#include ')
			x.append(include)
			x.newLine
		}
		if (!enhancedClass.includes.empty) {
			x.newLine
		}
		return x.toString
	}

	def compileForwardDeclarations(ClassDefinition cls) {
		'// TODO forward declarations\n'
	}

	def compile(BaseClassName base) {
		var result = 'public ' + base.qualifiedName.compile
		if (base.templateArgument != null) {
			result = result + '<' + base.templateArgument.compile + '>'
		}
		result
	}

	private def indent(int count) {
		indentationCount = Math::max(0, indentationCount + count)
	}

	def removeTrailingBlankLines(StringBuilder sb) {
		while (sb.length != 0 && CharMatcher::WHITESPACE.matches(sb.charAt(sb.length - 1))) {
			sb.deleteCharAt(sb.length - 1)
		}
	}

	/**
* Appends a line break to the end of the StringBuilder and then inserts the
* indentation given by (the class-wide) indentationCount.
*/
	def newLine(StringBuilder sb) {
		sb.append(System::getProperty("line.separator"))
		sb.append(Strings::repeat('    ', indentationCount))
	}

	def newLine(StringBuilder sb, boolean onlyIf) {
		if (onlyIf) {
			sb.newLine
		}
	}

	def newLine(StringBuilder sb, int count) {
		for (i : 1 .. count) {
			sb.newLine
		}
	}

	def newLine(StringBuilder sb, int count, boolean onlyIf) {
		if (onlyIf) {
			sb.newLine(count)
		}
	}

	def compile(QualifiedName full) {
		full.namespaces.join('', '::', '::', [it]) + full.name
	}

	def compile(Literal literal) {
		if (literal.number != null)
			return literal.number
		if (literal.char != null)
			return literal.char.value
		if (literal.string != null)
			return literal.string
	}

	def String compile(MemberInitialization init) {
		if (init.literal != null)
			return init.literal.compile
		if (init.variable != null)
			return init.variable.compile
		if (init.array != null && !init.array.empty)
			return '{' + init.array.map[compile].join(', ') + '}'
	}

	def String compile(ArrayDeclaration array) {
		val x = new StringBuilder
		x.append('[')
		if (array.int != null)
			x.append(array.int)
		if (array.variable != null)
			x.append(array.variable.compile)
		x.append(']')
		if (array.next != null)
			x.append(array.next.compile)
		return x.toString
	}

	def compile(MemberDeclaration decl, boolean compileInitialzation) {
		val x = new StringBuilder
		x.append(decl.name)
		if (decl.array != null)
			x.append(decl.array.compile)
		if (compileInitialzation && decl.init != null) {
			x.append(' = ')
			x.append(decl.init.compile)
		}
		return x.toString
	}

	def String compile(TemplateArgument arg) {
		val x = new StringBuilder
		if (arg.type != null) {
			x.append(arg.type.compile)
			if (arg.isFunction) {
				x.append('(')
				x.append(arg.parameters.map[compile].join(', '))
				x.append(')')
			}
		}
		if (arg.literal != null)
			x.append(arg.literal.compile)
		if (arg.hasNext) {
			x.append(', ')
			x.append(arg.next.compile)
		}
		return x.toString
	}

	def String compile(QualifiedTypeName type) {
		val x = new StringBuilder
		x.append(type.name)
		if (type.templateArgument != null) {
			x.append('<')
			x.append(type.templateArgument.compile)
			x.append('>')
		}
		if (type.pointer != null)
			x.append(type.pointer)
		if (type.hasNext) {
			x.append('::')
			x.append(type.nextType.compile)
		}
		return x.toString
	}

	def compile(TypeName type) {
		val x = new StringBuilder
		if (type.const)
			x.append('const ')
		if (type.unsigned)
			x.append('unsigned ')
		x.append(type.type.compile)
		return x.toString
	}

	def compile(Parameter param) {
		return param.type.compile + ' ' + param.name
	}

	def compileDeclaration(Member member) {
		val x = new StringBuilder

		if (member.comment != null)
			x.append(member.comment.compileComment)
		if (member.static)
			x.append('static ')
		x.append(member.type.compile)
		x.append(' ')
		x.append(member.variables.map[compile(!member.static)].join(', '))
		x.append(';')

		return x.toString
	}

	def compileDefinition(Member member, String containingClassName) {
		if (!member.static)
			throw new IllegalArgumentException("non-static members are defined on declaration")

		val x = new StringBuilder

		x.append(member.type.compile)
		x.append(' ')
		x.append(containingClassName + '::')
		x.append(member.variables.map[compile(true)].join(', '))
		x.append(';')

		return x.toString
	}

	def compile(TypeDef typeDef) {
		val x = new StringBuilder
		if (typeDef.comment != null) {
			x.append(typeDef.comment.compileComment)
			x.newLine
		}
		if (typeDef.enumeration) {
			x.append('enum class ')
			x.append(typeDef.name)
			x.append(' { ')
			x.append(typeDef.enums.join(', '))
			x.append(' }')
		} else {
			x.append('typedef ')
			x.append(typeDef.type.compile)
			x.append(' ')
			x.append(typeDef.name)
			x.append(';')
		}
		return x.toString
	}

	def String compileComment(String comment/*, int indentBySpaces*/) {
		val MAX_LINE_LENGTH = 80
		val originalLines = comment.substring(1, comment.length - 1).replace('\r', '').split('\n')
		val trimmed = originalLines.map[trim].join('\n')
		val paragraphs = trimmed.split("\n\n+")
		val lines = new ArrayList<String>
		for (p : paragraphs) {
			val words = p.replaceAll('[\r\n\t ]+', ' ').split(' ')
			val line = new StringBuilder
			val word = words.iterator
			while (word.hasNext) {
				val w = word.next
				if (line.length + w.length > MAX_LINE_LENGTH) {
					line.deleteCharAt(line.length - 1)
					lines.add(line.toString)

					// clear the current line after inserting it
					line.setLength(0)
				}

				// append the last line comprising at least the last word from the loop
				line.append(w)
				line.append(' ')
			}
			line.deleteCharAt(line.length - 1)
			lines.add(line.toString)
		}

		if (lines.size == 0)
			'/** */'
		else if (lines.size == 1)
			'/** ' + lines.head + ' */'
		else {
			val x = new StringBuilder
			x.append('/**')
			x.newLine
			for (line : lines) {
				x.append(' * ')
				x.append(line)
				x.newLine
			}
			x.append(' */')
			return x.toString
		}
	}

	def compileDeclaration(Method method, boolean isInterfaceMethod) {
		val x = new StringBuilder

		if (method.comment != null) {
			x.append(method.comment.compileComment)
			x.newLine
		}
		if (method.template != null)
			x.append(method.template.compile)
		if (method.static)
			x.append('static ')
		if (isInterfaceMethod || method.virtual)
			x.append('virtual ')
		x.append(method.returnType.compile)
		x.append(' ')
		x.append(method.name)
		x.append('(')
		x.append(method.parameters.map[compile].join(', '))
		x.append(')')
		if (method.const)
			x.append(' const')
		if (isInterfaceMethod)
			x.append(' = 0')
		x.append(';')

		return x.toString
	}

	def compileDeclaration(EnhancedMethod method) {
		val x = new StringBuilder

		if (method.original.comment != null) {
			x.append(method.original.comment.compileComment)
			x.newLine
		}
		if (method.original.template != null) {
			x.append(method.original.template.compile)
			x.newLine
		}
		if (method.original.static)
			x.append('static ')
		if (method.virtual)
			x.append('virtual ')
		x.append(method.original.returnType.compile)
		x.append(' ')
		x.append(method.original.name)
		x.append('(')
		x.append(method.original.parameters.map[compile].join(', '))
		x.append(')')
		if (method.original.const)
			x.append(' const')
		if (method.pureVirtual)
			x.append(' = 0')
		if (method.overrides)
			x.append(' override')
		x.append(';')

		return x.toString
	}

	def compile(TemplateParameters tp) {
		'template <' + tp.parameters.map[compile].join(', ') + '>'
	}

	def compile(TemplateParameter tp) {
		if (tp.typeName != null)
			tp.typeName + ' ' + tp.parameterName
		else
			'typename ' + tp.parameterName
	}

	def compile(TypeName type, List<String> typeDefNames, String className) {
		val x = new StringBuilder
		if (type.const)
			x.append('const ')
		if (type.unsigned)
			x.append('unsigned ') // TODO
		val compiled = type.type.compile
		if (typeDefNames.contains(compiled))
			x.append(className + '::')
		x.append(compiled)
		return x.toString
	}

	def compileDefinition(Method method, ClassDefinition cls, List<String> typeDefNames) {
		val x = new StringBuilder

		if (cls.template != null) {
			x.append(cls.template.compile)
			x.newLine
		}

		if (method.template != null) {
			x.append(method.template.compile)
			x.newLine
		}
		x.append(method.returnType.compile(typeDefNames, cls.qualifiedName.name))
		x.append(' ')
		x.append(cls.qualifiedName.name)
		if (cls.template != null) {
			x.append('<')
			x.append(cls.template.parameters.map[parameterName].join(', '))
			x.append('>')
		}
		x.append('::')
		x.append(method.name)
		x.append('(')
		x.append(method.parameters.map[compile].join(', '))
		x.append(')')
		if (method.const)
			x.append(' const')
		x.append(' {')
		x.newLine
		x.append('}')

		return x.toString
	}

	def compileDeclaration(Constructor ctor, String containingClassName, (String)=>String idToType) {
		val x = new StringBuilder

		if (ctor.comment != null)
			x.append(ctor.comment.compileComment)
		x.append(containingClassName)
		x.append('(')
		x.append(ctor.initializers.map[compile(idToType)].join(', '))
		x.append(')')
		x.append(';')

		return x.toString
	}

	private def compile(ConstructorInitializer init, (String)=>String idToType) {
		if (init.parameter != null)
			init.parameter.compile
		else
			idToType.apply(init.variable) + ' ' + init.variable
	}

	def compileDefinition(Constructor ctor, String containingClassName, (String)=>String idToType) {
		val x = new StringBuilder

		x.append(containingClassName)
		x.append('::')
		x.append(containingClassName)

		x.append('(')
		x.append(ctor.initializers.map[compile(idToType)].join(', '))
		x.append(' {')
		x.newLine
		x.append('}')

		return x.toString
	}

	def compileDefinition(Constructor ctor, String containingClassName, (String)=>String idToType,
		TemplateParameters template) {
		val x = new StringBuilder

		if (template != null && !template.parameters.empty) {
			x.append(template.compile)
			x.newLine
		}
		x.append(containingClassName)
		if (template != null && !template.parameters.empty) {
			x.append('<')
			x.append(template.parameters.map[parameterName].join(', '))
			x.append('>')
		}
		x.append('::')
		x.append(containingClassName)

		x.append('(')
		x.append(ctor.initializers.map[compile(idToType)].join(', '))
		x.append(') {')
		x.newLine
		x.append('}')
		x.newLine(2)

		return x.toString
	}
}